/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.admin.web.mvc.governance;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.admin.governance.service.ConsumerService;
import com.alibaba.dubbo.admin.governance.service.OverrideService;
import com.alibaba.dubbo.admin.governance.service.ProviderService;
import com.alibaba.dubbo.admin.governance.service.RouteService;
import com.alibaba.dubbo.admin.registry.common.domain.ConsumerPO;
import com.alibaba.dubbo.admin.registry.common.domain.OverridePO;
import com.alibaba.dubbo.admin.registry.common.domain.ProviderPO;
import com.alibaba.dubbo.admin.registry.common.domain.RoutePO;
import com.alibaba.dubbo.admin.registry.common.route.OverrideUtils;
import com.alibaba.dubbo.admin.registry.common.route.RouteRule;
import com.alibaba.dubbo.admin.registry.common.route.RouteRule.MatchPair;
import com.alibaba.dubbo.admin.registry.common.route.RouteUtils;
import com.alibaba.dubbo.admin.web.mvc.BaseController;
import com.alibaba.dubbo.admin.web.pulltool.Tool;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * ConsumersController. URI: /services/$service/consumers
 *
 */
@Controller
@RequestMapping("/governance/consumers")
public class ConsumersController extends BaseController {

    @Autowired
    private ProviderService providerService;

    @Autowired
    private ConsumerService consumerService;

    @Autowired
    private OverrideService overrideService;

    @Autowired
    private RouteService routeService;

    @RequestMapping("")
    public String index(HttpServletRequest request, HttpServletResponse response,
                      Model model) {
        prepare(request, response, model, "index", "consumers");
        List<ConsumerPO> consumerPOS;
        List<OverridePO> overridePOS;
        List<ProviderPO> providerPOS = null;
        List<RoutePO> routePOS = null;
        BindingAwareModelMap newModel = (BindingAwareModelMap)model;
        String service = (String)newModel.get("service");
        String address = (String)newModel.get("address");
        String application = (String)newModel.get("app");
        // service
        if (service != null && service.length() > 0) {
            consumerPOS = consumerService.findByService(service);
            overridePOS = overrideService.findByService(service);
            providerPOS = providerService.findByService(service);
            routePOS = routeService.findByService(service);
        }
        // address
        else if (address != null && address.length() > 0) {
            consumerPOS = consumerService.findByAddress(address);
            overridePOS = overrideService.findByAddress(Tool.getIP(address));
        }
        // application
        else if (application != null && application.length() > 0) {
            consumerPOS = consumerService.findByApplication(application);
            overridePOS = overrideService.findByApplication(application);
        }
        // all
        else {
            consumerPOS = consumerService.findAll();
            overridePOS = overrideService.findAll();
        }
        if (consumerPOS != null && consumerPOS.size() > 0) {
            for (ConsumerPO consumerPO : consumerPOS) {
                if (service == null || service.length() == 0) {
                    providerPOS = providerService.findByService(consumerPO.getService());
                    routePOS = routeService.findByService(consumerPO.getService());
                }
                List<RoutePO> routed = new ArrayList<RoutePO>();
                consumerPO.setProviderPOS(RouteUtils
                    .route(consumerPO.getService(), consumerPO.getAddress(), consumerPO.getParameters(), providerPOS, overridePOS, routePOS, null, routed));
                consumerPO.setRoutePOS(routed);
                OverrideUtils.setConsumerOverrides(consumerPO, overridePOS);
            }
        }
        model.addAttribute("consumers", consumerPOS);
        return "governance/screen/consumers/index";
    }

    @RequestMapping("/{id}")
    public String show(@PathVariable("id") Long id,
                       HttpServletRequest request, HttpServletResponse response, Model model) {
        prepare(request, response, model, "show", "consumers");
        ConsumerPO consumerPO = consumerService.findConsumer(id);
        List<ProviderPO> providerPOS = providerService.findByService(consumerPO.getService());
        List<RoutePO> routePOS = routeService.findByService(consumerPO.getService());
        List<OverridePO> overridePOS = overrideService.findByService(consumerPO.getService());
        List<RoutePO> routed = new ArrayList<RoutePO>();
        consumerPO.setProviderPOS(RouteUtils.route(consumerPO.getService(), consumerPO.getAddress(), consumerPO.getParameters(), providerPOS, overridePOS, routePOS, null, routed));
        consumerPO.setRoutePOS(routed);
        OverrideUtils.setConsumerOverrides(consumerPO, overridePOS);
        model.addAttribute("consumer", consumerPO);
        model.addAttribute("providers", consumerPO.getProviderPOS());
        model.addAttribute("routes", consumerPO.getRoutePOS());
        model.addAttribute("overrides", consumerPO.getOverridePOS());
        return "governance/screen/consumers/show";
    }

    @RequestMapping("/{id}/edit")
    public String edit(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response,  Model model) {
        prepare(request, response, model, "edit", "consumers");
        ConsumerPO consumerPO = consumerService.findConsumer(id);
        List<ProviderPO> providerPOS = providerService.findByService(consumerPO.getService());
        List<RoutePO> routePOS = routeService.findByService(consumerPO.getService());
        List<OverridePO> overridePOS = overrideService.findByService(consumerPO.getService());
        List<RoutePO> routed = new ArrayList<RoutePO>();
        consumerPO.setProviderPOS(RouteUtils.route(consumerPO.getService(), consumerPO.getAddress(), consumerPO.getParameters(), providerPOS, overridePOS, routePOS, null, routed));
        consumerPO.setRoutePOS(routed);
        OverrideUtils.setConsumerOverrides(consumerPO, overridePOS);
        model.addAttribute("consumer", consumerPO);
        model.addAttribute("providers", consumerPO.getProviderPOS());
        model.addAttribute("routes", consumerPO.getRoutePOS());
        model.addAttribute("overrides", consumerPO.getOverridePOS());
        return "governance/screen/consumers/edit";
    }

    @RequestMapping(value = "/update", method = RequestMethod.POST) //post
    public String update(@ModelAttribute ConsumerPO newConsumerPO, HttpServletRequest request, HttpServletResponse response, Model model) {
        prepare(request, response, model, "update", "consumers");
        boolean success = true;
        Long id = newConsumerPO.getId();
        String parameters = newConsumerPO.getParameters();
        ConsumerPO consumerPO = consumerService.findConsumer(id);
        if (consumerPO == null) {
            model.addAttribute("message", getMessage("NoSuchOperationData", id));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "governance/consumers");
            return "governance/screen/redirect";
        }
        String service = consumerPO.getService();
        if (!super.currentUserPO.hasServicePrivilege(service)) {
            model.addAttribute("message", getMessage("HaveNoServicePrivilege", service));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "governance/consumers");
            return "governance/screen/redirect";
        }
        Map<String, String> oldMap = StringUtils.parseQueryString(consumerPO.getParameters());
        Map<String, String> newMap = StringUtils.parseQueryString(parameters);
        for (Map.Entry<String, String> entry : oldMap.entrySet()) {
            if (entry.getValue().equals(newMap.get(entry.getKey()))) {
                newMap.remove(entry.getKey());
            }
        }
        String address = consumerPO.getAddress();
        List<OverridePO> overridePOS = overrideService.findByServiceAndAddress(consumerPO.getService(), consumerPO.getAddress());
        OverrideUtils.setConsumerOverrides(consumerPO, overridePOS);
        OverridePO overridePO = consumerPO.getOverridePO();
        if (overridePO != null) {
            if (newMap.size() > 0) {
                overridePO.setParams(StringUtils.toQueryString(newMap));
                overridePO.setEnabled(true);
                overridePO.setOperator(operator);
                overridePO.setOperatorAddress(operatorAddress);
                overrideService.updateOverride(overridePO);
            } else {
                overrideService.deleteOverride(overridePO.getId());
            }
        } else {
            overridePO = new OverridePO();
            overridePO.setService(service);
            overridePO.setAddress(address);
            overridePO.setParams(StringUtils.toQueryString(newMap));
            overridePO.setEnabled(true);
            overridePO.setOperator(operator);
            overridePO.setOperatorAddress(operatorAddress);
            overrideService.saveOverride(overridePO);
        }
        model.addAttribute("success", success);
        model.addAttribute("redirect", "governance/consumers");
        return "governance/screen/redirect";
    }

    @RequestMapping("/{id}/routed")
    public String routed(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response, Model model) {
        prepare(request, response, model, "routed", "consumers");
        showDetail(id, request, response, model);
        return "governance/screen/consumers/routed";
    }

    @RequestMapping("/{id}/notified")
    public String notified(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response, Model model) {
        prepare(request, response, model, "notified", "consumers");
        showDetail(id, request, response, model);
        return "governance/screen/consumers/notified";
    }

    @RequestMapping("/{id}/overrided")
    public String overrided(@PathVariable("id") Long id, HttpServletRequest request, HttpServletResponse response, Model model) {
        prepare(request, response, model, "overrided", "consumers");
        showDetail(id, request, response, model);
        return "governance/screen/consumers/overrided";
    }

    @RequestMapping("/{ids}/shield")
    public String shield(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return mock(ids, "force:return null", "shield", request, response, model);
    }

    @RequestMapping("/{ids}/tolerant")
    public String tolerant(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return mock(ids, "fail:return null", "tolerant", request, response, model);
    }

    @RequestMapping("/{ids}/recover")
    public String recover(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return mock(ids,  "", "recover", request, response, model);
    }

    private String mock(Long[] ids, String mock, String methodName, HttpServletRequest request,
                         HttpServletResponse response, Model model) throws Exception {
        prepare(request, response, model, methodName, "consumers");
        boolean success = true;
        if (ids == null || ids.length == 0) {
            model.addAttribute("message", getMessage("NoSuchOperationData"));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "../../consumers");
            return "governance/screen/redirect";
        }
        List<ConsumerPO> consumerPOS = new ArrayList<ConsumerPO>();
        for (Long id : ids) {
            ConsumerPO c = consumerService.findConsumer(id);
            if (c != null) {
                consumerPOS.add(c);
                if (!super.currentUserPO.hasServicePrivilege(c.getService())) {
                    model.addAttribute("message", getMessage("HaveNoServicePrivilege", c.getService()));
                    success = false;
                    model.addAttribute("success", success);
                    model.addAttribute("redirect", "../../consumers");
                    return "governance/screen/redirect";
                }
            }
        }
        for (ConsumerPO consumerPO : consumerPOS) {
            String service = consumerPO.getService();
            String address = Tool.getIP(consumerPO.getAddress());
            List<OverridePO> overridePOS = overrideService.findByServiceAndAddress(service, address);
            if (overridePOS != null && overridePOS.size() > 0) {
                for (OverridePO overridePO : overridePOS) {
                    Map<String, String> map = StringUtils.parseQueryString(overridePO.getParams());
                    if (mock == null || mock.length() == 0) {
                        map.remove("mock");
                    } else {
                        map.put("mock", URL.encode(mock));
                    }
                    if (map.size() > 0) {
                        overridePO.setParams(StringUtils.toQueryString(map));
                        overridePO.setEnabled(true);
                        overridePO.setOperator(operator);
                        overridePO.setOperatorAddress(operatorAddress);
                        overrideService.updateOverride(overridePO);
                    } else {
                        overrideService.deleteOverride(overridePO.getId());
                    }
                }
            } else if (mock != null && mock.length() > 0) {
                OverridePO overridePO = new OverridePO();
                overridePO.setService(service);
                overridePO.setAddress(address);
                overridePO.setParams("mock=" + URL.encode(mock));
                overridePO.setEnabled(true);
                overridePO.setOperator(operator);
                overridePO.setOperatorAddress(operatorAddress);
                overrideService.saveOverride(overridePO);
            }
        }
        model.addAttribute("success", success);
        model.addAttribute("redirect", "../../consumers");
        return "governance/screen/redirect";
    }

    private void showDetail( Long id,
                             HttpServletRequest request, HttpServletResponse response, Model model) {
        ConsumerPO consumerPO = consumerService.findConsumer(id);
        List<ProviderPO> providerPOS = providerService.findByService(consumerPO.getService());
        List<RoutePO> routePOS = routeService.findByService(consumerPO.getService());
        List<OverridePO> overridePOS = overrideService.findByService(consumerPO.getService());
        List<RoutePO> routed = new ArrayList<RoutePO>();
        consumerPO.setProviderPOS(RouteUtils.route(consumerPO.getService(), consumerPO.getAddress(), consumerPO.getParameters(), providerPOS, overridePOS, routePOS, null, routed));
        consumerPO.setRoutePOS(routed);
        OverrideUtils.setConsumerOverrides(consumerPO, overridePOS);
        model.addAttribute("consumer", consumerPO);
        model.addAttribute("providers", consumerPO.getProviderPOS());
        model.addAttribute("routes", consumerPO.getRoutePOS());
        model.addAttribute("overrides", consumerPO.getOverridePOS());
    }

    @RequestMapping("/allshield")
    public String allshield(@RequestParam(required = false) String service, HttpServletRequest request,
                                                   HttpServletResponse response, Model model) throws Exception {
        return allmock(service,  "force:return null", "allshield",request, response, model);
    }

    @RequestMapping("/alltolerant")
    public String alltolerant(@RequestParam(required = false) String service, HttpServletRequest request,
                               HttpServletResponse response, Model model) throws Exception {
        return allmock(service, "fail:return null", "alltolerant", request, response, model);
    }

    @RequestMapping("/allrecover")
    public String allrecover(@RequestParam(required = false) String service, HttpServletRequest request,
                              HttpServletResponse response, Model model) throws Exception {
        return allmock(service, "", "allrecover", request, response, model);
    }

    private String allmock(String service, String mock, String methodName, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        prepare(request, response, model, methodName,"consumers");
        boolean success = true;
        if (service == null || service.length() == 0) {
            model.addAttribute("message", getMessage("NoSuchOperationData"));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "../consumers");
            return "governance/screen/redirect";
        }
        if (!super.currentUserPO.hasServicePrivilege(service)) {
            model.addAttribute("message", getMessage("HaveNoServicePrivilege", service));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "../consumers");
            return "governance/screen/redirect";
        }
        List<OverridePO> overridePOS = overrideService.findByService(service);
        OverridePO allOverridePO = null;
        if (overridePOS != null && overridePOS.size() > 0) {
            for (OverridePO overridePO : overridePOS) {
                if (overridePO.isDefault()) {
                    allOverridePO = overridePO;
                    break;
                }
            }
        }
        if (allOverridePO != null) {
            Map<String, String> map = StringUtils.parseQueryString(allOverridePO.getParams());
            if (mock == null || mock.length() == 0) {
                map.remove("mock");
            } else {
                map.put("mock", URL.encode(mock));
            }
            if (map.size() > 0) {
                allOverridePO.setParams(StringUtils.toQueryString(map));
                allOverridePO.setEnabled(true);
                allOverridePO.setOperator(operator);
                allOverridePO.setOperatorAddress(operatorAddress);
                overrideService.updateOverride(allOverridePO);
            } else {
                overrideService.deleteOverride(allOverridePO.getId());
            }
        } else if (mock != null && mock.length() > 0) {
            OverridePO overridePO = new OverridePO();
            overridePO.setService(service);
            overridePO.setParams("mock=" + URL.encode(mock));
            overridePO.setEnabled(true);
            overridePO.setOperator(operator);
            overridePO.setOperatorAddress(operatorAddress);
            overrideService.saveOverride(overridePO);
        }
        model.addAttribute("success", success);
        model.addAttribute("redirect", "../consumers");
        return "governance/screen/redirect";
    }

    @RequestMapping("/{ids}/allow")
    public String allow(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return access(request, response, ids, model, true, false, "allow");
    }

    @RequestMapping("/{ids}/forbid")
    public String forbid(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return access(request, response, ids, model, false, false, "forbid");
    }

    @RequestMapping("/{ids}/onlyallow")
    public String onlyallow(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return access(request, response, ids, model, true, true, "onlyallow");
    }

    @RequestMapping("/{ids}/onlyforbid")
    public String onlyforbid(@PathVariable("ids") Long[] ids, HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
        return access(request, response, ids, model, false, true, "onlyforbid");
    }

    private String access(HttpServletRequest request, HttpServletResponse response, Long[] ids,
                           Model model, boolean allow, boolean only, String methodName) throws Exception {
        prepare(request, response, model, methodName, "consumers");
        boolean success = true;
        if (ids == null || ids.length == 0) {
            model.addAttribute("message", getMessage("NoSuchOperationData"));
            success = false;
            model.addAttribute("success", success);
            model.addAttribute("redirect", "../../consumers");
            return "governance/screen/redirect";
        }
        List<ConsumerPO> consumerPOS = new ArrayList<ConsumerPO>();
        for (Long id : ids) {
            ConsumerPO c = consumerService.findConsumer(id);
            if (c != null) {
                consumerPOS.add(c);
                if (!super.currentUserPO.hasServicePrivilege(c.getService())) {
                    model.addAttribute("message", getMessage("HaveNoServicePrivilege", c.getService()));
                    success = false;
                    model.addAttribute("success", success);
                    model.addAttribute("redirect", "../../consumers");
                    return "governance/screen/redirect";
                }
            }
        }
        Map<String, Set<String>> serviceAddresses = new HashMap<String, Set<String>>();
        for (ConsumerPO consumerPO : consumerPOS) {
            String service = consumerPO.getService();
            String address = Tool.getIP(consumerPO.getAddress());
            Set<String> addresses = serviceAddresses.get(service);
            if (addresses == null) {
                addresses = new HashSet<String>();
                serviceAddresses.put(service, addresses);
            }
            addresses.add(address);
        }
        for (Map.Entry<String, Set<String>> entry : serviceAddresses.entrySet()) {
            String service = entry.getKey();
            boolean isFirst = false;
            List<RoutePO> routePOS = routeService.findForceRouteByService(service);
            RoutePO routePO = null;
            if (routePOS == null || routePOS.size() == 0) {
                isFirst = true;
                routePO = new RoutePO();
                routePO.setService(service);
                routePO.setForce(true);
                routePO.setName(service + " blackwhitelist");
                routePO.setFilterRule("false");
                routePO.setEnabled(true);
            } else {
                routePO = routePOS.get(0);
            }
            Map<String, MatchPair> when = null;
            MatchPair matchPair = null;
            if (isFirst) {
                when = new HashMap<String, MatchPair>();
                matchPair = new MatchPair(new HashSet<String>(), new HashSet<String>());
                when.put("consumer.host", matchPair);
            } else {
                when = RouteRule.parseRule(routePO.getMatchRule());
                matchPair = when.get("consumer.host");
            }
            if (only) {
                matchPair.getUnmatches().clear();
                matchPair.getMatches().clear();
                if (allow) {
                    matchPair.getUnmatches().addAll(entry.getValue());
                } else {
                    matchPair.getMatches().addAll(entry.getValue());
                }
            } else {
                for (String consumerAddress : entry.getValue()) {
                    if (matchPair.getUnmatches().size() > 0) { // whitelist take effect
                        matchPair.getMatches().remove(consumerAddress); // remove data in blacklist
                        if (allow) { // if allowed
                            matchPair.getUnmatches().add(consumerAddress); // add to whitelist
                        } else { // if not allowed
                            matchPair.getUnmatches().remove(consumerAddress); // remove from whitelist
                        }
                    } else { // blacklist take effect
                        if (allow) { // if allowed
                            matchPair.getMatches().remove(consumerAddress); // remove from blacklist
                        } else { // if not allowed
                            matchPair.getMatches().add(consumerAddress); // add to blacklist
                        }
                    }
                }
            }
            StringBuilder sb = new StringBuilder();
            RouteRule.contidionToString(sb, when);
            routePO.setMatchRule(sb.toString());
            routePO.setUsername(operator);
            if (matchPair.getMatches().size() > 0 || matchPair.getUnmatches().size() > 0) {
                if (isFirst) {
                    routeService.createRoute(routePO);
                } else {
                    routeService.updateRoute(routePO);
                }
            } else if (!isFirst) {
                routeService.deleteRoute(routePO.getId());
            }
        }
        model.addAttribute("success", success);
        model.addAttribute("redirect", "../../consumers");
        return "governance/screen/redirect";
    }
}
